import os
import json
import requests
from pathlib import Path
from pptx import Presentation
from pptx.enum.shapes import MSO_SHAPE_TYPE
from pptx.util import Pt
import re
import logging
import time

# 导入 Glossary 模块
from glossary_module import Glossary  # 假设你把前面 Glossary 类保存为 glossary_module.py

# 设置日志
logger = logging.getLogger(__name__)

# ====== 用户输入 ======
api_url = input("请输入 API URL: ").strip()
api_key = input("请输入 API Key: ").strip()
model_name = input("请输入模型名称（如 gpt-4、gpt-3.5-turbo）: ").strip()
source_lang = input("请输入源语言代码（如 zh-cn, en, fr），留空则自动检测: ").strip().lower()
target_lang = input("请输入目标语言代码（如 zh-cn, en, fr）: ").strip().lower()
glossary_csv_paths = input("请输入术语表 CSV 文件路径（多个文件用逗号分隔，可留空）: ").strip()


def load_glossaries(paths_str, target_lang_out):
    """加载多个glossary文件，类似参考代码的实现"""
    loaded_glossaries = []

    if not paths_str:
        return loaded_glossaries

    paths_list = paths_str.split(",")
    for p_str in paths_list:
        file_path = Path(p_str.strip())

        if not file_path.exists():
            logger.error(f"Glossary file not found: {file_path}")
            print(f"警告：术语表文件不存在: {file_path}")
            continue

        if not file_path.is_file():
            logger.error(f"Glossary path is not a file: {file_path}")
            print(f"警告：术语表路径不是文件: {file_path}")
            continue

        try:
            glossary_obj = Glossary.from_csv(file_path, target_lang_out)
            if glossary_obj.entries:
                loaded_glossaries.append(glossary_obj)
                logger.info(f"Loaded glossary '{glossary_obj.name}' with {len(glossary_obj.entries)} entries.")
                print(f"成功加载术语表 '{glossary_obj.name}'，包含 {len(glossary_obj.entries)} 条术语")
            else:
                logger.info(
                    f"Glossary '{file_path.stem}' loaded with no applicable entries for lang_out '{target_lang_out}'.")
                print(f"术语表 '{file_path.stem}' 已加载但没有适用于目标语言 '{target_lang_out}' 的术语")

        except Exception as e:
            logger.error(f"Failed to load glossary from {file_path}: {e}")
            print(f"错误：加载术语表失败 {file_path}: {e}")

    return loaded_glossaries


# 加载所有glossary
glossaries = load_glossaries(glossary_csv_paths, target_lang)
print(f"总共加载了 {len(glossaries)} 个术语表")


def apply_glossary_translations(text, glossaries_list):
    """应用所有术语表的翻译，类似参考代码的方式"""
    if not text or not glossaries_list:
        return text

    modified_text = text
    all_matches = []

    # 收集所有术语表的匹配项
    for glossary in glossaries_list:
        matches = glossary.get_active_entries_for_text(text)
        if matches:
            all_matches.extend(matches)
            logger.debug(f"Found {len(matches)} matches in glossary '{glossary.name}' for text: {text[:50]}...")

    if not all_matches:
        return text

    # 按源词长度排序，优先处理长词汇（避免短词覆盖长词）
    all_matches = sorted(set(all_matches), key=lambda x: len(x[0]), reverse=True)

    # 应用所有匹配的术语替换
    replacement_count = 0
    for source_term, target_term in all_matches:
        # 使用更精确的替换策略
        # 1. 精确匹配（完整单词）
        if source_term in modified_text:
            # 检查是否为完整单词边界（对于英文等使用空格分隔的语言）
            import re
            if re.search(r'\b' + re.escape(source_term) + r'\b', modified_text, re.IGNORECASE):
                modified_text = re.sub(
                    r'\b' + re.escape(source_term) + r'\b',
                    target_term,
                    modified_text,
                    flags=re.IGNORECASE
                )
                replacement_count += 1
                logger.debug(f"Applied glossary replacement: '{source_term}' -> '{target_term}'")
            # 对于中文等不使用空格的语言，直接替换
            elif not re.search(r'[a-zA-Z]', source_term):  # 非英文词汇
                modified_text = modified_text.replace(source_term, target_term)
                replacement_count += 1
                logger.debug(f"Applied glossary replacement: '{source_term}' -> '{target_term}'")

    if replacement_count > 0:
        logger.info(f"Applied {replacement_count} glossary replacements to text: {text[:50]}...")

    return modified_text


# ====== 调用自定义 LLM API ======
def call_custom_api(text):
    max_retries = 3
    retry_delay = 2

    # 构建更严格的提示词
    source_name = {"zh-cn": "Chinese", "en": "English", "fr": "French", "ja": "Japanese", "de": "German"}.get(
        source_lang, source_lang)
    target_name = {"zh-cn": "Chinese", "en": "English", "fr": "French", "ja": "Japanese", "de": "German"}.get(
        target_lang, target_lang)

    # 构建包含术语表信息的提示词
    glossary_info = ""
    if glossaries:
        # 为当前文本收集相关术语
        relevant_terms = []
        for glossary in glossaries:
            matches = glossary.get_active_entries_for_text(text)
            relevant_terms.extend(matches)

        if relevant_terms:
            # 去重并构建术语表提示
            unique_terms = list(set(relevant_terms))
            term_list = "\n".join([f"- {src} → {tgt}" for src, tgt in unique_terms[:10]])  # 限制显示前10个术语
            glossary_info = f"""

IMPORTANT TERMINOLOGY:
Please use these specific translations for the following terms:
{term_list}
"""

    prompt = f"""Translate the following text from {source_name} to {target_name}.{glossary_info}

IMPORTANT RULES:
1. Only return the translated text, no explanations or additional content
2. If the text is empty or contains only spaces/punctuation, return it as-is
3. Preserve original formatting and line breaks
4. Do not add phrases like "Here is the translation:" or "翻译结果："
5. Maintain the same tone and style as the original
6. Follow the terminology guidelines provided above strictly

Text to translate:
{text}"""

    payload = {
        "model": model_name,
        "messages": [
            {"role": "system",
             "content": "You are a professional translator. Only return the direct translation without any additional commentary or formatting. Follow terminology guidelines strictly."},
            {"role": "user", "content": prompt},
        ],
        "temperature": 0.1,
        "max_tokens": len(text) * 3 + 100
    }

    headers = {
        "Accept": "application/json",
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
    }

    # 添加重试机制
    for attempt in range(max_retries):
        try:
            response = requests.post(api_url, headers=headers, json=payload, timeout=60)
            response.raise_for_status()
            data = response.json()

            translated = data["choices"][0]["message"]["content"].strip()

            # 清理不需要的前缀
            unwanted_prefixes = [
                "翻译结果：", "以下是翻译结果：", "翻译：", "Translation:", "Here is the translation:",
                "The translation is:", "Translated text:", "翻译内容：", "译文：", "翻译后："
            ]

            for prefix in unwanted_prefixes:
                if translated.startswith(prefix):
                    translated = translated[len(prefix):].strip()

            return translated

        except Exception as e:
            if attempt < max_retries - 1:
                print(f"API调用失败，第{attempt + 1}次重试... 错误：{str(e)}")
                time.sleep(retry_delay)
                retry_delay *= 2  # 指数退避
            else:
                raise e


# ====== 文本预处理和验证 ======
def should_translate(text):
    """判断文本是否需要翻译"""
    if not text or not text.strip():
        return False

    # 如果只包含数字、符号、空格，不翻译
    if re.match(r'^[\s\d\W]*$', text):
        return False

    # 文本太短（少于2个字符）可能不需要翻译
    clean_text = re.sub(r'[\s\W]', '', text)
    if len(clean_text) < 2:
        return False

    return True


# ====== 文本翻译函数 ======
def translate_text(text):
    if not should_translate(text):
        return text

    original_text = text

    try:
        # 1. 首先应用术语表替换（预处理）
        text_with_glossary = apply_glossary_translations(text, glossaries)

        # 2. 调用API进行翻译（API内部也会考虑术语表）
        translated = call_custom_api(text)

        # 3. 后处理：再次应用术语表确保一致性
        final_translation = apply_glossary_translations(translated, glossaries)

        # 验证翻译结果
        if len(final_translation) > len(original_text) * 5:  # 翻译结果异常长
            print(f"警告：翻译结果异常长，原文：{original_text[:50]}...")
            return original_text

        if any(phrase in final_translation.lower() for phrase in [
            "i'm sorry", "i cannot", "please provide", "you have not provided",
            "could you please", "it seems like", "no text to translate"
        ]):
            print(f"警告：检测到错误翻译，保持原文：{original_text[:50]}...")
            return original_text

        # 记录翻译信息
        if text_with_glossary != text:
            logger.info(f"Pre-applied glossary to: {text[:30]}... -> {text_with_glossary[:30]}...")
        if final_translation != translated:
            logger.info(f"Post-applied glossary to: {translated[:30]}... -> {final_translation[:30]}...")

        return final_translation

    except Exception as e:
        print(f"翻译失败，保持原文：{original_text[:50]}... 错误：{str(e)}")
        logger.error(f"Translation failed for text: {original_text[:50]}... Error: {str(e)}")
        return original_text


# ====== 保持格式的文本处理 ======
def get_font_color(font):
    """安全获取字体颜色"""
    try:
        if hasattr(font.color, 'rgb') and font.color.rgb is not None:
            return font.color.rgb
        elif hasattr(font.color, 'theme_color') and font.color.theme_color is not None:
            return ('theme', font.color.theme_color)
    except:
        pass
    return None


def set_font_color(font, color_data):
    """安全设置字体颜色"""
    try:
        if color_data is None:
            return
        if isinstance(color_data, tuple) and color_data[0] == 'theme':
            font.color.theme_color = color_data[1]
        else:
            font.color.rgb = color_data
    except:
        pass


def translate_text_with_formatting(text_frame):
    """保持格式的文本翻译"""
    # 先检查是否有文本需要翻译
    full_text = text_frame.text.strip()
    if not should_translate(full_text):
        return

    # 收集所有段落和格式信息
    paragraphs_data = []

    for paragraph in text_frame.paragraphs:
        runs_data = []
        paragraph_level = paragraph.level if hasattr(paragraph, 'level') else 0

        for run in paragraph.runs:
            # 安全获取字体属性
            try:
                font_name = run.font.name
            except:
                font_name = None

            try:
                font_size = run.font.size
            except:
                font_size = None

            try:
                bold = run.font.bold
            except:
                bold = None

            try:
                italic = run.font.italic
            except:
                italic = None

            try:
                underline = run.font.underline
            except:
                underline = None

            color_data = get_font_color(run.font)

            run_info = {
                'original_text': run.text,
                'translated_text': translate_text(run.text) if should_translate(run.text) else run.text,
                'font_name': font_name,
                'font_size': font_size,
                'bold': bold,
                'italic': italic,
                'underline': underline,
                'color': color_data
            }
            runs_data.append(run_info)

        paragraphs_data.append({
            'runs': runs_data,
            'level': paragraph_level
        })

    # 清空文本框
    text_frame.clear()

    # 重新构建文本框，保持格式
    for para_idx, para_data in enumerate(paragraphs_data):
        if para_idx == 0:
            # 使用第一个段落
            paragraph = text_frame.paragraphs[0]
        else:
            # 添加新段落
            paragraph = text_frame.add_paragraph()

        # 设置段落级别
        if hasattr(paragraph, 'level'):
            paragraph.level = para_data['level']

        # 添加runs
        first_run = True
        for run_data in para_data['runs']:
            if first_run:
                # 使用段落的第一个run
                if len(paragraph.runs) > 0:
                    run = paragraph.runs[0]
                else:
                    run = paragraph.add_run()
                first_run = False
            else:
                run = paragraph.add_run()

            run.text = run_data['translated_text']

            # 安全恢复格式
            try:
                if run_data['font_name']:
                    run.font.name = run_data['font_name']
            except:
                pass

            try:
                if run_data['font_size']:
                    run.font.size = run_data['font_size']
            except:
                pass

            try:
                if run_data['bold'] is not None:
                    run.font.bold = run_data['bold']
            except:
                pass

            try:
                if run_data['italic'] is not None:
                    run.font.italic = run_data['italic']
            except:
                pass

            try:
                if run_data['underline'] is not None:
                    run.font.underline = run_data['underline']
            except:
                pass

            set_font_color(run.font, run_data['color'])


# ====== 增强的形状检测和处理函数 ======
def is_shape_with_text(shape):
    """检测形状是否包含文本"""
    try:
        return (hasattr(shape, 'text_frame') and
                shape.text_frame is not None and
                hasattr(shape.text_frame, 'text') and
                shape.text_frame.text.strip())
    except:
        return False


def get_shape_info(shape):
    """获取形状的详细信息"""
    info = {
        'has_text': False,
        'shape_type': None,
        'is_group': False,
        'is_table': False,
        'is_chart': False,
        'text': ''
    }

    try:
        info['shape_type'] = shape.shape_type

        if hasattr(shape, 'shapes'):  # 组合对象
            info['is_group'] = True

        elif shape.shape_type == MSO_SHAPE_TYPE.TABLE:
            info['is_table'] = True

        elif shape.shape_type == MSO_SHAPE_TYPE.CHART:
            info['is_chart'] = True

        if is_shape_with_text(shape):
            info['has_text'] = True
            info['text'] = shape.text_frame.text

    except Exception as e:
        logger.debug(f"Error getting shape info: {e}")

    return info


def process_group_shape(group_shape):
    """处理组合形状"""
    processed_count = 0
    try:
        if hasattr(group_shape, 'shapes'):
            for sub_shape in group_shape.shapes:
                if is_shape_with_text(sub_shape):
                    print(f"  处理组合中的子形状文本: {sub_shape.text_frame.text[:30]}...")
                    translate_text_with_formatting(sub_shape.text_frame)
                    processed_count += 1
                # 递归处理嵌套组合
                elif hasattr(sub_shape, 'shapes'):
                    processed_count += process_group_shape(sub_shape)
    except Exception as e:
        logger.error(f"Error processing group shape: {e}")

    return processed_count


def process_table_shape(table_shape):
    """处理表格形状"""
    processed_count = 0
    try:
        if hasattr(table_shape, 'table'):
            for row_idx, row in enumerate(table_shape.table.rows):
                for col_idx, cell in enumerate(row.cells):
                    if cell.text.strip():
                        print(f"  处理表格单元格 ({row_idx + 1},{col_idx + 1}): {cell.text[:30]}...")
                        translate_text_with_formatting(cell.text_frame)
                        processed_count += 1
    except Exception as e:
        logger.error(f"Error processing table shape: {e}")

    return processed_count


def process_single_shape(shape):
    """处理单个形状"""
    try:
        shape_info = get_shape_info(shape)

        print(f"  检测到形状类型: {shape_info['shape_type']}")

        if shape_info['is_group']:
            print("  处理组合形状...")
            return process_group_shape(shape)

        elif shape_info['is_table']:
            print("  处理表格形状...")
            return process_table_shape(shape)

        elif shape_info['has_text']:
            print(f"  处理文本形状: {shape_info['text'][:50]}...")
            translate_text_with_formatting(shape.text_frame)
            return 1

    except Exception as e:
        logger.error(f"Error processing shape: {e}")
        print(f"  处理形状时出错: {str(e)}")

    return 0


# ====== 翻译函数 ======
def translate_ppt(input_path, output_path):
    print(f"正在加载PPT文件: {input_path}")
    prs = Presentation(input_path)

    total_slides = len(prs.slides)
    total_processed = 0

    print(f"发现 {total_slides} 页幻灯片")

    for slide_idx, slide in enumerate(prs.slides):
        print(f"\n正在处理第 {slide_idx + 1}/{total_slides} 页...")

        slide_processed = 0
        shape_count = len(slide.shapes)
        print(f"  发现 {shape_count} 个形状")

        # 遍历所有形状
        for shape_idx, shape in enumerate(slide.shapes):
            try:
                print(f"  正在处理形状 {shape_idx + 1}/{shape_count}...")
                processed = process_single_shape(shape)
                slide_processed += processed

                # 添加小延迟避免API限制
                if processed > 0:
                    time.sleep(0.5)

            except Exception as e:
                print(f"  处理形状 {shape_idx + 1} 时出错：{str(e)}")
                logger.error(f"Error processing shape {shape_idx + 1} on slide {slide_idx + 1}: {str(e)}")
                continue

        total_processed += slide_processed
        print(f"  第 {slide_idx + 1} 页完成，处理了 {slide_processed} 个文本元素")

    print(f"\n正在保存翻译后的PPT文件: {output_path}")
    prs.save(output_path)
    print(f"翻译完成！总共处理了 {total_processed} 个文本元素")
    print(f"已保存到：{output_path}")


# ====== 主入口，异常捕获 JSON 输出 ======
if __name__ == "__main__":
    # 设置基本日志配置
    logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')

    try:
        input_ppt = input("请输入要翻译的 PPT 文件路径: ").strip()
        output_ppt = input("请输入翻译后 PPT 保存路径: ").strip()

        if not os.path.exists(input_ppt):
            raise FileNotFoundError(f"输入文件不存在: {input_ppt}")

        # 显示加载的术语表摘要
        if glossaries:
            print(f"\n=== 术语表摘要 ===")
            for glossary in glossaries:
                print(f"术语表: {glossary.name}, 术语数: {len(glossary.entries)}")
            print("==================\n")

        translate_ppt(input_ppt, output_ppt)

        print(json.dumps({
            "success": True,
            "msg": "翻译完成",
            "output": os.path.abspath(output_ppt),
            "glossaries_loaded": len(glossaries)
        }, ensure_ascii=False))

    except Exception as e:
        logger.error(f"Main execution error: {str(e)}")
        print(json.dumps({
            "success": False,
            "msg": str(e),
            "output": "",
            "glossaries_loaded": 0
        }, ensure_ascii=False))